# -*- coding:utf-8 -*-

# @File     :api_v2
# @Author   :Yupeng
# @Date     :2019/2/27 11:03
import os
import uuid
import hashlib
import zipfile
import tarfile
import rarfile

from django.http import HttpRequest
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q

from apps.accounts.auth import login_required_api
from hte.error.handle import HTEError, HTEException
from hte.utils.general import *
from .models.asset import ClassAsset, Asset
from .models.task import VisualRecognitionTask, Task
from .models.vr_model import VisualRecognitionModel
from hte.settings import MEDIA_ROOT
from .pic_recog.handle import pic_training, test_images, img_to_list
from .tasks import visual_recognition_task, add, mul


@require_methods_api(['POST'])
def test_lyp(request: HttpRequest):
    '''
    这个是lyp的测试api，其他人员不用在意，可以保留或删除
    :param request:
    :return:
    '''
    if request.method == 'POST':
        files = request.FILES.getlist('files')
        for file in files:
            tf = tarfile.open(file, 'r')
            tf.list()
            print(tf.list())
            tf.close()
        # file = '/media/robyn/BA4AE3BC4AE37393/wolf.tar'
        # tf = tarfile.open(file, 'r')
        # tf.list()
        # tf.close()
        return json_response('aaa')


def _calculate_hash(file):
    '''
    计算出当前文件的内容的hash值，然后查数据库是否有这样hash值的文件存在，
    如果有，则不需要上传（但是前端做到让用户感觉是上传了）
    :param file:
    :return:
    '''
    file.seek(0)
    m = hashlib.md5()
    while True:
        data = file.read(1024)
        if not data:
            break
        m.update(data)
    hash_value = m.hexdigest()
    file.seek(0)
    return hash_value


def _asset_store_or_not(file, author, hash_value, a_list, dir=False, pressed_archive=False):
    '''
    处理当前文件是否存储的问题
    :param file:
    :param author:
    :param hash_value: 文件计算得到的hash值
    :param a_list: 一个数组
    :param dir: 是否为路径，从压缩包里处理完的图片则为True,直接上传图片则不需要传递该参数，默认为False
    :param pressed_archive: 是否为压缩包
    :return:
    '''
    remove_list = list()
    remove = False
    try:
        file_exist = Asset.objects.get(hash_value=hash_value)
    except Exception:
        file_exist = None
    temp = dict()
    if file_exist:  # 这个图片已经存储的情况下：其实还要分该类的图片路径中是否包含了该路径
        # temp[file_exist.id] = file_exist.image_path
        temp['id'] = file_exist.id
        temp['name'] = file_exist.filename
        temp['path'] = file_exist.image_path
        # if file_exist.id ==
        # remove_list.append(file_exist.image_path)
        remove = True
    else:  # 这个图片还没有上传过
        if dir:
            # 对形如这样的路径作简化存储： F:\MyGit\htedata\services\hte\media\unpressed_images\animal4.zip\animal4\8.jpg，只保留后面部分
            if len(file.split('/')) - 1 > 1:
                element = file.split('/')
                media_loacation = element.index('hte')
                file = '/'.join(element[media_loacation + 2:])
            if len(file.split('\\')) - 1 > 1:
                element = file.split('\\')
                media_loacation = element.index('hte')
                file = '/'.join(element[media_loacation + 2:])

        asset = Asset(file=file, author=author, hash_value=hash_value)
        try:
            asset.save()
            # temp[asset.id] = asset.image_path
            temp['id'] = asset.id
            temp['name'] = asset.filename
            temp['path'] = asset.image_path
        except Exception:
            raise
    if pressed_archive:
        pass
    else:
        a_list.append(temp)
    return remove


@login_required_api
@require_methods_api(['POST', 'GET', 'DELETE'])
def asset_list(request: HttpRequest):
    '''
    目前先只允许图片上传
    POST:上传图片
    GET:用户获取他自己的所有图片
    DELETE:用户删除他自己的图片
    TODO:检验上传的文件是否为压缩包或图片
    :param request:
    :return:
    '''
    if request.method == 'POST':
        files = request.FILES.getlist('files')
        author = request.user
        # 创建分类结束之后上传图片需要下面这个参数，个人资源处上传则不需要
        class_asset_id = request.POST.get('class_asset_id')
        # ids_and_path = list()
        id_name_path = list()
        user_upload_num = len(files)
        for file in files:
            # temp_dicc = dict()
            ext = os.path.splitext(file.name)[1].upper()
            if ext not in ('.ZIP', '.JAR', '.PNG', '.JPEG', '.JPG', '.BMP', '.PSD', '.SVG'):
                abort_on_error(HTEError.BAD_FILE_TYPE)

            if ext in ('.PNG', '.JPEG', '.JPG', '.BMP', '.PSD', '.SVG'):
                hash_value = _calculate_hash(file)
                _asset_store_or_not(file, author, hash_value, id_name_path)
            '''
            如果用户上传的是压缩包，则需要将压缩包先解压到unpressed_images中对应名字的文件加下，然后分别对各个图片做哈希处理，如果该图片已经存在，就将此处的这个图片删除
            '''
            if ext in ('.ZIP', '.JAR'):
                hash_value = _calculate_hash(file)
                _asset_store_or_not(file, author, hash_value, id_name_path, pressed_archive=True)
                if ext == '.ZIP':
                    zt = zipfile.ZipFile(file, 'r')
                else:
                    zt = zipfile.ZipFile(file, 'r')
                path = os.path.join(settings.MEDIA_ROOT, 'unpressed_images')
                path = os.path.join(path, file.name)  # 以压缩包的名字新建一个文件夹（用father表示），path代表路径。一般都是xxx.zip或者xxx.tar形式
                zt.extractall(path=path)
                zt.close()

                dir_and_file = os.listdir(path)  # 列出该文件夹下的所有文件和子文件夹
                for i in range(len(dir_and_file)):
                    son = os.path.join(path, dir_and_file[i])
                    if os.path.isdir(son):  # 如果father下的儿子是文件夹（路径）
                        son_dir_and_file = os.listdir(son)  # 再列出该儿子文件夹下的所有文件和子文件夹
                        for j in range(len(son_dir_and_file)):
                            grandson = os.path.join(son, son_dir_and_file[j])
                            if os.path.isfile(grandson):
                                # 执行对图片文件的依次存入
                                with open(grandson, 'rb') as file_now:
                                    # file_now = open(grandson, 'rb')
                                    hash_value = _calculate_hash(file_now)  # 计算哈希值需要做个读取
                                    remove = _asset_store_or_not(grandson, author, hash_value, id_name_path,
                                                                 True)  # 做字段初始化则就是需要字符型
                                # file_now.close()
                                if remove:
                                    os.remove(grandson)
                            else:  # 如果两层都不是图片文件则报错，不允许更多嵌套的压缩包
                                abort_on_error(HTEError.BAD_PRESSED_ARCHIVE)
                    elif os.path.isfile(son):  # 如果father下的儿子是文件
                        file_now = open(son, 'rb')
                        hash_value = _calculate_hash(file_now)
                        remove = _asset_store_or_not(son, author, hash_value, id_name_path, True)
                        if remove:
                            os.remove(son)
        if class_asset_id:
            try:
                class_asset = ClassAsset.objects.get(id=class_asset_id)
                if class_asset.images_path is None:
                    class_asset.images_path = list()
                for x in id_name_path:
                    # new_path = list(x.values())[0]
                    new_path = x['path']
                    if new_path in class_asset.images_path:
                        pass
                    else:
                        class_asset.images_path.append(new_path)
                class_asset.num_of_pictures = len(class_asset.images_path)
                class_asset.save()
                result = dict()
                result['num_of_pictures'] = len(class_asset.images_path)
                result['id_name_path'] = str(id_name_path)
                return json_response(result)
            except ClassAsset.DoesNotExist:
                abort_on_error(HTEError.CLASS_ASSET_NOT_FOUND)
        return json_response(str(id_name_path))

    elif request.method == 'GET':
        # author = get_param('author')
        page = get_param('page', convert_to=int)
        # page = get_json_field_r(request, 'page', int)
        assets = Asset.objects.filter(author=request.user.username)
        paginator = Paginator(assets, 10)
        try:
            assets = paginator.page(page)
        except (EmptyPage, PageNotAnInteger):
            page = 1
            assets = paginator.page(1)
        result = [x.to_dict() for x in assets]
        return json_response({
            'page_count': paginator.num_pages,
            'page_current': page,
            'total_count': paginator.count,
            'result': result,
        })
    elif request.method == 'DELETE':
        # TODO:压缩包可以直接删除，但是图片不行，因为图片可能正在被某个任务使用
        asset_id = get_json_field_r(request, 'asset_id', int)
        # if not Asset.objects.filter(id=asset_id).exists():
        #     abort_on_error(HTEError.PICTURE_NOT_FOUND)
        try:
            asset = Asset.objects.get(id=asset_id)
            this_id = asset.id
            asset.delete()
            return json_response(['Delete successfully', this_id])
        except Asset.DoesNotExist:
            abort_on_error(HTEError.PICTURE_NOT_FOUND)
        # 应该再抛出一个图片正在被使用的异常
        except Exception:
            abort_on_error(HTEError.PICTURE_IS_USED)


@login_required_api
@require_methods_api(['POST', 'GET', 'DELETE'])
def vr_task_list(request: HttpRequest):
    '''
    VisualRecognitionTask
    用户点击创建图像识别任务则立即发送API请求并获取到返回的任务ID，如果用户最后没有点击保存或者训练则将该条记录删除
    POST:创建一个图像识别任务,并且自动添加一个test分类
    GET:获取所有的图像分类任务
    DELETE:删除某个任务
    :param request:
    :return:
    '''
    if request.method == 'POST':
        # title = get_json_field_r(request, 'title', str, allow_none=True)
        author = request.user
        # task_type = get_json_field_r(request, 'task_type', int, allow_none=True)
        # classes_and_asset = get_json_field_r(request, 'classes_and_asset', list, allow_none=True)
        try:
            vrtask = VisualRecognitionTask(author=author)
            vrtask.save()
            # title = 'Default' + str(vrtask.id)
            # 实例化任务之后应该开始通知执行算法，如果开始运行，则status=1,并依据任务的运行状态调整为status=2 or status=3
            try:
                class_asset = ClassAsset(title='test', author=author, vrtask_id=vrtask.id)
                class_asset.save()
            except Exception:
                raise
        except Exception:
            raise
        result = dict()
        result['vrtask_id'] = vrtask.id
        result['test_class_id'] = class_asset.id
        return json_response(result)
    elif request.method == 'GET':
        # fsch modified 2019-3-18
        page = get_param('page', convert_to=int)
        # desc = get_param('desc')
        isAsc = get_param('sort')
        if isAsc == 'false':
            isAsc = False
        else:
            isAsc = True
        # page = get_json_field_r(request, 'page', int)
        if isAsc:
            all_vr_tasks = VisualRecognitionTask.objects.filter(author=request.user.username).order_by('created_time')  # 升序
        else:
            all_vr_tasks = VisualRecognitionTask.objects.filter(author=request.user.username).order_by('-created_time')  # 降序
        paginator = Paginator(all_vr_tasks, 5)
        # print(paginator.num_pages)
        try:
            vr_tasks = paginator.page(page)
        except (EmptyPage, PageNotAnInteger):
            vr_tasks = paginator.page(1)
        data = {'count': len(all_vr_tasks), 'total_pages': paginator.num_pages}
        result = [x.to_dict() for x in vr_tasks]
        data['result'] = result
        return json_response(data)
        # try:
        #     vr_tasks = paginator.page(page)
        # except (EmptyPage, PageNotAnInteger):
        #     page = 1
        #     vr_tasks = paginator.page(1)
        # data = {'page_count': paginator.num_pages, 'page_current': page, 'total_count': paginator.count}
        # result = [x.to_dict() for x in vr_tasks]
        # data['result'] = result
        # return json_response(data)
    elif request.method == 'DELETE':
        # vrtask_id = get_param('vrtask_id')
        vrtask_id = get_json_field_r(request, 'vrtask_id')
        print(vrtask_id)
        try:
            vr_task = VisualRecognitionTask.objects.get(id=vrtask_id)
            print(vr_task)
            # task = Task.objects.get(id=vrtask_id)
            vr_task.delete()  # 注释掉自己写的delete()方法
            # task.delete()
            result = ['Deleted successfully', vr_task.to_dict()]
            return json_response(result)
        except VisualRecognitionTask.DoesNotExist:
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)


@login_required_api
@require_methods_api(['GET', 'PATCH'])
def vr_task_one(request: HttpRequest):
    '''
    GET:获取某个图像识别任务,并连同它的分类以及训练好的模型一起返回
    PATCH:编辑某个图像识别任务,需要特别注意的是：如果用户创建任务之后直接关掉而且不保存的话也要调用PATCH，而且要将save字段设置为false
    :param request:
    :return:
    '''
    if request.method == 'GET':
        vrtask_id = get_param('vrtask_id')
        # vrtask_id = get_json_field_r(request, 'vrtask_id', int)
        result = dict()
        try:
            vr_task = VisualRecognitionTask.objects.get(id=vrtask_id)
            # result.append(vr_task.to_dict())
            result['vrtask'] = vr_task.to_dict()
            # return json_response([vr_task.to_dict(), x.to_dict() for x in class_assets])
        except VisualRecognitionTask.DoesNotExist:
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)
        class_assets = ClassAsset.objects.filter(vrtask_id=vrtask_id)
        # result.append([x.to_dict() for x in class_assets])
        result['classes'] = [x.to_dict() for x in class_assets]
        vr_model = VisualRecognitionModel.objects.filter(vrtask_id=vrtask_id)
        result['model'] = ([x.to_dict() for x in vr_model])
        # result['model']=vr_model.to_dict()
        return json_response(result)
    elif request.method == 'PATCH':
        vrtask_id = get_json_field_r(request, 'vrtask_id', int)
        title = get_json_field_r(request, 'title', str, allow_none=True, default='Default vrtask')
        description = get_json_field_r(request, 'description', str, allow_none=True)
        save = get_json_field_r(request, 'save', bool, allow_none=True, default=True)
        # result = get_json_field_r(request, 'result', dict, allow_none=True)
        try:
            vr_task = VisualRecognitionTask.objects.get(id=vrtask_id)
            if save:
                if title:
                    vr_task.title = title
                if description:
                    vr_task.description = description
                vr_task.save()
            else:
                vr_task.delete()
            return json_response(vr_task.to_dict())
        except VisualRecognitionTask.DoesNotExist:
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)


@login_required_api
@require_methods_api(['POST', 'GET', 'DELETE'])
def class_asset_list(request: HttpRequest):
    '''
    创建任务后创建分类，直接就定义好图片的路径，无论是新上传或是选择已有图片，都放在同一个文件夹下
    POST:创建一个分类,和创建任务类似，点击之后直接返回分类ID
    GET:用户点击进入任务，显示所有分类
    DELETE:删除某个分类
    :param request:
    :return:
    '''
    if request.method == 'POST':
        # title = get_json_field_r(request, 'title', str)
        author = request.user
        vrtask_id = get_json_field_r(request, 'vrtask_id', int)
        if not VisualRecognitionTask.objects.filter(id=vrtask_id).exists():
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)
        try:
            # class_asset = ClassAsset(title=title, author=author, tag=tag, description=description,
            #                          vrtask_id=vrtask_id, images_path=images_path, num_of_pictures=num_of_pictures)
            class_asset = ClassAsset(vrtask_id=vrtask_id, author=author)
            class_asset.save()
            return json_response(class_asset.id)
        except Exception:
            raise

    elif request.method == 'GET':
        vrtask_id = get_param('vrtask_id', convert_to=int)
        # vrtask_id = get_json_field_r(request, 'vrtask_id', int)
        if not VisualRecognitionTask.objects.filter(id=vrtask_id).exists():
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)
        page = get_param('page', allow_none=True)
        class_assets = ClassAsset.objects.filter(vrtask_id=vrtask_id)
        paginator = Paginator(class_assets, 10)
        try:
            class_assets = paginator.page(page)
        except (EmptyPage, PageNotAnInteger):
            class_assets = paginator.page(1)
        result = [x.to_dict() for x in class_assets]
        return json_response(result)
    elif request.method == 'DELETE':
        class_asset_id = get_param('class_asset_id', convert_to=int)
        # class_asset_id = get_json_field_r(request, 'class_asset_id', int)
        try:
            class_asset = ClassAsset.objects.get(id=class_asset_id)
            if class_asset.title == 'test':
                abort_on_error(HTEError.CAN_NOT_BE_DELETED)
            class_asset.delete()
            return json_response(['Delete successfully', class_asset_id])
        except ClassAsset.DoesNotExist:
            abort_on_error(HTEError.CLASS_ASSET_NOT_FOUND)


@login_required_api
@require_methods_api(['GET', 'PATCH'])
def class_asset_one(request: HttpRequest):
    '''
    PATCH:针对某个分类做操作：编辑其他介绍性信息
    GET:用户点击进入某个分类，显示该分类的所有信息和图片
    :param request:
    :return:
    '''
    if request.method == 'PATCH':
        class_asset_id = get_json_field_r(request, 'class_asset_id', int, allow_none=True)
        title = get_json_field_r(request, 'title', str, allow_none=True, default='Default class')
        tag = get_json_field_r(request, 'tag', str, allow_none=True)
        description = get_json_field_r(request, 'description', str, allow_none=True)
        save = get_json_field_r(request, 'save', bool, allow_none=True, default=True)
        is_test = False
        try:
            class_asset = ClassAsset.objects.get(id=class_asset_id)
            if class_asset.title == 'test':
                is_test = True
            if save:
                if title:
                    class_asset.title = title
                if tag:
                    class_asset.tag = tag
                if description:
                    class_asset.description = description
                if is_test:
                    class_asset.title = 'test'
                class_asset.save()
            else:
                class_asset.delete()
            return json_response(class_asset.to_dict())
        except ClassAsset.DoesNotExist:
            abort_on_error(HTEError.CLASS_ASSET_NOT_FOUND)
    elif request.method == 'GET':
        class_asset_id = get_param('class_asset_id')
        # class_asset_id = get_json_field_r(request, 'class_asset_id', int)
        try:
            class_asset = ClassAsset.objects.get(id=class_asset_id)

            # if class_asset.images_path
            return json_response(class_asset.to_dict())
        except ClassAsset.DoesNotExist:
            abort_on_error(HTEError.CLASS_ASSET_NOT_FOUND)


@login_required_api
@require_methods_api(['POST', 'GET', 'DELETE'])
def visual_model_list(request: HttpRequest):
    '''
    POST:训练任务完成后创建模型
    GET:获取某人的所有模型，某个任务的所有模型在获取任务的时候已经显示
    DELETE：删除某个模型
    :param request:
    :return:
    '''
    if request.method == 'POST':
        # title = get_json_field_r(request, 'title', str, allow_none=True)
        number_of_classes = get_json_field_r(request, 'number_of_classes', int, allow_none=True)
        number_of_images = get_json_field_r(request, 'number_of_images', int, allow_none=True)
        classes = get_json_field_r(request, 'classes', list, allow_none=True)
        description = get_json_field_r(request, 'description', allow_none=True)
        path = get_json_field_r(request, 'path', str)
        vrtask_id = get_json_field_r(request, 'vrtask_id', int)
        accuracy = get_json_field_r(request, 'accuracy', float)
        try:
            vr_task = VisualRecognitionTask.objects.get(id=vrtask_id)
            author = vr_task.author
        except VisualRecognitionTask.DoesNotExist:
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)

        try:
            vr_model = VisualRecognitionModel(number_of_classes=number_of_classes, number_of_images=number_of_images,
                                              classes=classes,
                                              description=description, path=path, vrtask_id=vrtask_id, author=author,
                                              accuracy=accuracy)
            vr_model.save()
            return json_response(vr_model.id)
        except Exception:
            raise

    elif request.method == 'GET':
        page = get_param('page', convert_to=int)
        # page = get_json_field_r(request, 'page', int)
        vr_models = VisualRecognitionModel.objects.filter(author=request.user.username)
        paginator = Paginator(vr_models, 10)
        try:
            vr_models = paginator.page(page)
        except (EmptyPage, PageNotAnInteger):
            page = 1
            vr_models = paginator.page(1)
        result = [x.to_dict() for x in vr_models]
        return json_response({
            'page_count': paginator.num_pages,
            'page_current': page,
            'total_count': paginator.count,
            'result': result,
        })
    elif request.method == 'DELETE':
        vr_model_id = get_json_field_r(request, 'vr_model_id', int)
        try:
            vr_model = VisualRecognitionModel.objects.get(id=vr_model_id)
            this_id = vr_model.id
            vr_model.delete()
            return json_response(['Delete successfully', this_id])
        except VisualRecognitionModel.DoesNotExist:
            abort_on_error(HTEError.VR_MODEL_NOT_FOUND)


@login_required_api
@require_methods_api(['GET', 'PATCH'])
def visual_model_one(request: HttpRequest):
    '''
    visual_recognition_model图片识别模型
    :param request:
    :return:
    '''
    if request.method == 'GET':
        vr_model_id = get_param('vr_model_id')
        # vr_model_id = get_json_field_r(request, 'vr_model_id', int)
        try:
            vr_model = VisualRecognitionModel.objects.get(id=vr_model_id)
            return json_response(vr_model.to_dict())
        except VisualRecognitionModel.DoesNotExist:
            abort_on_error(HTEError.VR_MODEL_NOT_FOUND)
    elif request.method == 'PATCH':
        vr_model_id = get_json_field_r(request, 'vr_model_id')
        description = get_json_field_r(request, 'description', str, allow_none=True)
        title = get_json_field_r(request, 'title', str, allow_none=True)
        try:
            vr_model = VisualRecognitionModel.objects.get(id=vr_model_id)
            vr_model.description = description
            vr_model.title = title
            vr_model.save()
            return json_response(vr_model.to_dict())
        except VisualRecognitionModel.DoesNotExist:
            abort_on_error(HTEError.VR_MODEL_NOT_FOUND)


@login_required_api
@require_methods_api(['GET'])
def vrtask_start(request: HttpRequest):
    '''
    前端点击开始任务，则将任务的ID传到后端，后端自行进行将要传递的参数的打包处理并调用算法
    :param request:
    :return:
    '''
    if request.method == 'GET':
        vrtask_id = get_param('vrtask_id', convert_to=int)
        result = dict()
        pic_list = list()
        classes_list = list()
        test_list = list()
        try:
            vr_task = VisualRecognitionTask.objects.get(id=vrtask_id)
        except VisualRecognitionTask.DoesNotExist:
            abort_on_error(HTEError.VR_TASK_NOT_FOUND)
        q = Q(vrtask_id=vrtask_id) & ~Q(title='test')
        class_assets = ClassAsset.objects.filter(q)
        if len(class_assets) < 1:
            abort_on_error(HTEError.NO_TRAIN_CLASS)

        for class_asset in class_assets:
            classes_list.append(class_asset.title)
            new_images_path = list()
            if class_asset.images_path is None:
                abort_on_error(HTEError.CLASS_HAS_NOT_IMAGE)
            for image_path in class_asset.images_path:
                new_image_path = os.path.join(MEDIA_ROOT, image_path)
                if '\\' in new_image_path:
                    new_image_path = new_image_path.replace('\\', '/')
                new_images_path.append(new_image_path)
            pic_list.append(new_images_path)
        result['pic_list'] = pic_list
        result['classes_list'] = classes_list
        #  图像识别任务用来训练的参数在这里已经准备好
        q = Q(vrtask_id=vrtask_id) & Q(title='test')
        class_asset = ClassAsset.objects.filter(q)
        for image_path in class_asset[0].images_path:
            new_image_path = os.path.join(MEDIA_ROOT, image_path)
            if '\\' in new_image_path:
                new_image_path = new_image_path.replace('\\', '/')
            test_list.append(new_image_path)
        result['test_list'] = test_list
        #  图像识别任务中测试的参数已经准备好
        vr_task_run = visual_recognition_task.delay(vrtask_id, pic_list, classes_list, test_list)
        # a = vr_task_run.get()
        vr_task.celery_task_id = vr_task_run.task_id
        vr_task.status = 1
        vr_task.save()
        # vr_task.task_id = visual_recognition_task.delay(vrtask_id, pic_list, classes_list, test_list).task_id
        return json_response(result)
